package clusteringAlg;

import java.util.ArrayList;
import java.util.List;
import java.util.Random;

public class DBScan extends SamplesSelector implements SampleSelectorInterface {

	int counter = 0;
	
	int maxChanges = 10;
	List<DBSample> samples = null;
	int realClusterCount = -1;
	List<Mean> clusters;
	double eps = -1;
	int minPts = -1;
	
	/**
	 * @return the realClusterCount
	 */
	public int getRealClusterCount() {
		return realClusterCount;
	}

	/**
	 * @param realClusterCount the realClusterCount to set
	 */
	public void setRealClusterCount(int realClusterCount) {
		this.realClusterCount = realClusterCount;
	}

	/**
	 * @return the eps
	 */
	public double getEps() {
		return eps;
	}

	/**
	 * @param eps the eps to set
	 */
	public void setEps(double eps) {
		this.eps = eps;
	}

	/**
	 * @return the minPts
	 */
	public int getMinPts() {
		return minPts;
	}

	/**
	 * @param minPts the minPts to set
	 */
	public void setMinPts(int minPts) {
		this.minPts = minPts;
	}
	
	public DBScan(Person person, int nClusters, int minPts, double eps) {
		super(person, nClusters, -1);
		
		this.minPts = minPts;
		this.eps = eps;

	}
	
	public DBScan(Person person, int nClusters, int minPts) {
		super(person, nClusters, -1);
		
		this.minPts = minPts;

	}

	public DBScan() {}
	
	public void initSamples(){

		if (samples == null){
			samples = new ArrayList<DBSample>();
			for (Sample s: person.getSamples()){
				samples.add(new DBSample(s));
			}
		}
		else{
			for (DBSample s: samples){
				s.setIdCluster(-1);
				s.setUsed(false);
			}
		}
		
		

	}
	
	public void initEps(){
		double max = Double.MIN_VALUE, min = Double.MAX_VALUE, dist;
		
		for (Sample s: person.getSamples()){
			for (Sample ss : person.getSamples()){
				dist = this.EukclideanDistance(s.getScaledImage(), ss.getScaledImage());
				if (dist > max){
					max = dist;
				}
				if (dist < min){
					min = dist;
				}
			}
		}
		
		this.setEps((max-min)/2);
	}
	
	private void DBscanAlg(double i){
		
		//List<DBSample> neighborPts;
		int neighbourPtsCount = 0;
		
		if (i==0){
			this.initEps();
		}
		
		if (this.getEps() <= 0){
			System.err.println("Minimalny pocet obrazov v clustri znizujeme, inak bude menej clustrov.  " );
			this.minPts--;
			this.initEps();
			i=-0.1;
		}
		
		this.initAlg();
		
		if (this.samples.size() < nClusters){
			System.err.println("Pocet vstupov musi byt vacsi ako pocet vystupov");
		}
		
		for (DBSample sample: this.samples){
			if (!sample.isUsed()){
				sample.setUsed(true);
				
				neighbourPtsCount = this.RegionQuery(sample);
				
				if (neighbourPtsCount >= this.minPts){
					
					this.ExpandCluster(sample);
					
				}else{
					for (DBSample sam: this.samples)
					{
						if(sam.getIdCluster() == this.realClusterCount)
							sam.setIdCluster(-1);
					}
				}
			}
		}
		
		//System.out.println("realc: " + realClusterCount + ", nC: " + nClusters + "counter: " + this.counter );
		
		if (counter < this.maxChanges || realClusterCount == nClusters){
			i = this.modifyEps(i);
			
			if (i == 0){
				this.CalcClusters();
			}else{
				DBscanAlg(i);
			}
		}
		else{
			if (realClusterCount > nClusters){
				do {
					this.removeSmallCluster();
				}while(realClusterCount > nClusters);

			}else{
				this.devideBiggestCluster(nClusters-(realClusterCount));
			}
			this.CalcClusters();
		}
	}
	
	private void removeSmallCluster(){
		int min = Integer.MAX_VALUE, index = -1;
		int samplesCount[] = new int[this.getRealClusterCount()];
		
		for (int i=0; i < this.getRealClusterCount(); i++ ){
			samplesCount[i]=0;
		}
		
		for (DBSample s: this.samples){
			if (s.getIdCluster()!=-1)
				samplesCount[s.getIdCluster()]++;
		}
		
		for (int i=0; i < this.getRealClusterCount(); i++ ){
			if (samplesCount[i] < min){
				min = samplesCount[i];
				index = i;
			}
		}
		
		for (DBSample s: this.samples){
			if (s.getIdCluster() == index)
				s.setIdCluster(-1);
			
			if (s.getIdCluster() == this.getRealClusterCount()-1){
				if (index != this.getRealClusterCount()-1)
					s.setIdCluster(index);
			}
		}
		
		this.setRealClusterCount(this.getRealClusterCount()-1);
	}
	
	private void devideBiggestCluster(int val){
		int max = Integer.MIN_VALUE, index = -1, temp=0;
		Random r = new Random();
		int samplesCount[] = new int[this.getRealClusterCount()];
		
		for (int i=0; i < this.getRealClusterCount(); i++ ){
			samplesCount[i]=0;
		}
		
		for (DBSample s: this.samples){
			if (s.getIdCluster()!=-1)
				samplesCount[s.getIdCluster()]++;
		}
		
		for (int i=0; i < this.getRealClusterCount(); i++ ){
			if (samplesCount[i] > max){
				max = samplesCount[i];
				index = i;
			}
		}
		
		for (DBSample s: this.samples){
			if (s.getIdCluster() == index){
				temp = (r.nextInt())%(val+1);
				s.setIdCluster( (temp == 0) ? s.getIdCluster() : this.getRealClusterCount()+temp-1);
			}
		}
		
		this.realClusterCount += (val);
	}
	
	private double modifyEps(double i){
		if (this.getRealClusterCount() > nClusters){
			
			if (i<=0){
				i=0.1;
				if (i!=0)
					counter++;
			}
			else{
				i = (i + 0.1*i);// ((i<=0.4) ? (i + 0.1*i) : 0.4);
			}
			
			this.eps += i;
			
			
		}else if (this.getRealClusterCount() < nClusters){
			
			if (i>=0){
				i=-0.1;
				if (i!=0)
					counter++;
			}
			else{
				 //i = ((i>=-0.4) ? (i + 0.1*i) : -0.4);
				 i = (i + 0.1*i);
			}
			
			this.eps += i;

		}else {
			i=0;
		}
		
		return i;
	}
	
	private void CalcClusters(){
		
		this.clusters = new ArrayList<Mean>();
		List<RelationSample> rs = null; 
		Sample sa = this.samples.get(0).clone();
		
		for (int i=0; i<this.getRealClusterCount(); i++){
			Mean m = new Mean(sa.getImage().clone(), sa.getPose());
			rs = new ArrayList<RelationSample>();;
			for (DBSample s : this.samples){
				if (s.getIdCluster() == i){
					rs.add(new RelationSample(s.clone(),1));
				}
			}
			m.setImages(rs);
			this.clusters.add(m);
		}
		
		for (Mean m: this.clusters){
			m.CalcMean();
			m.SetImageFromScaledImage(0, 255);
		}
	};
	
	private void ExpandCluster(DBSample s){
		
		s.setUsed(true);
		s.setIdCluster(this.getRealClusterCount());
		
		for (DBSample sample: this.samples)	{
			if (this.realClusterCount == sample.getIdCluster() && !sample.isUsed()){
				sample.setUsed(true);
				this.RegionQuery(sample);
			}
		}
		
		this.realClusterCount++;
	}
		
	private int RegionQuery(DBSample s){
		
		//List<DBSample> neighborPts = new ArrayList<DBSample>();
		int counter=0;
		
		for (DBSample DBs: this.samples){
			if (!DBs.isUsed()){
				if (this.EukclideanDistance(s.getScaledImage(), DBs.getScaledImage()) < this.eps){
					//DBs.setUsed(true);
					DBs.setIdCluster(realClusterCount);
					counter++;
				}
			}
		}
		
		return counter;
		//return neighborPts;
	}
	
	private void initAlg(){
		
		//clusters = new ArrayList<Mean>();
		initSamples();
		this.realClusterCount=0;
		this.ScaleData(0, 1);
	}

	@Override
	public void Run() {
		
		this.DBscanAlg(0);
		
	}

	@Override
	public List<Sample> GetMeanSamples() {
		List<Sample> samples = new ArrayList<Sample>();
		
		for (Sample m: this.clusters){
			samples.add(m);
		}
		
		return samples;
	}

	@Override
	public List<Sample> GetClosestSamples() {
		List<Sample> samples = this.GetMeanSamples();
		double min, dst;
		int counter = 0;
		
		for (Sample n: this.GetMeanSamples()){
		
			min=Double.MAX_VALUE;
			
			for (Sample m: this.person.getSamples()){
				dst = this.EukclideanDistance(m.getScaledImage(), n.getScaledImage());
				
				if (dst < min){
					samples.set(counter, m);
					min = dst;
				}
			}
			counter++;
		}
		
		return samples;
	}

}
